サーバーレスでルート探索付き地図サービスを自前で提供する方法を考えてみた
はじめに
アイキャッチ画像は、初めて精密な日本地図を作ったとされる伊能忠敬と日本地図です。いくつになっても信念を持って何かをひたむきに行う人に、私もなりたいものです。
さて、Webサービスを提供する際、地図を表示したいということは多々あるかと思います。地図で、様々な地点を一覧表示したり、ルートを表示できたりすると、ユーザ体験が向上するというのは少なくないかと思います。
こういった地図サービスをWebサービスに埋め込んで提供したい場合に、まず考えられるのがGoogle Maps Platformかと思います。
ちょっと最近大きく変更があったようですが、依然として一定の品質の地図、徒歩/公共交通機関/車でのルート探索、ストリートビュー等々、国内で提供されている最大級の地図サービスであることは異論ないかと思います。
単にユーザとしてGoogleMapをアプリなどで使用する場合、すべて無料なので特に問題はないかと思います。しかし、GoogleMapをサービスに埋め込んで提供する場合は、APIによっては費用が発生します。
https://cloud.google.com/maps-platform/pricing/sheet/?hl=ja
毎月200米ドルの無料枠があり、小さなサービスでは無料枠で収まるかと思います。しかしながら、それを超えてしまった場合、利用するAPIによってはそれなりの金額になる場合があります。
例えば、このような埋め込みでのルート探索表示では、無料枠を超えた場合、まず1表示で0.014米ドル発生します。およそ1円強なので、これは少なくない金額になってしまうかと思います。
GoogleMap以外では、Yahoo地図やmapboxが似たサービスになるかと思います。しかし、これらでもそこそこの費用が発生し、またそれぞれ商用利用やログインなどを設けたプライベートなページで表示させるには、料金を問い合わせなければならないようです。
では、いっその事自前で地図基盤を持ってしまえばどうなるんでしょうか?それもサーバーレスで構築した場合はどうなるんでしょう?
この記事では、ランニングコストを抑えるために自前でルート探索機能付き地図を提供したい、しかもサーバーレスで行った場合にどうすればいいのか、というのを考えたいと思います。
結論
はじめに私が調べた限りでの結論から申し上げたいと思います。
サーバーレスですべて自前でルート探索機能付き地図を提供するのは、課題点や制限があり、少し難しいかと思われます。別途既存サービスを利用したり、サーバーレスでない構成を含めることを検討してみてください。
ただ、課題点や制限を、乗り越え/許容できるのであれば、おそらくは安価に地図を提供することができるかと思われます。
地図サービスの作り方
まず、自前でルート探索機能付き地図サービスを提供するには、何が必要でしょうか。
サンプルコードの位置を変えただけですが、この記事で構築できる地図サービスのサンプル図を次に表したいと思います。
https://codepen.io/anon/pen/ROrMzo
秋葉原駅からDevelopers.IO CAFEへの車でのルート探索ですが、ちゃんとロータリーの一方通行なども認識されていることが確認できるかと思います。
上記の地図を提供するためには、フロントエンドでもバックエンドでも、大きく分けて、「地図画像を提供/表示する」ことと、「ルート探索機能を提供/表示する」ことが必要になるかと思います。
サンプルのフロントエンド側では、地図のスクロールや拡大縮小機能を提供し、範囲内の地図画像を引っ張ってくる、というWeb上での地図の基本機能を提供してくれるライブラリとして、「Leaflet」を利用しています。また、その上にルートを表示してくれるライブラリとして、「Leaflet Routing Machine」を利用しています。
フロントエンド側をサーバーレスのシステムとして提供するには、単にライブラリを使うだけなので問題はないかと思います。問題はバックエンド側になります。
サンプルのバックエンド側では、地図画像を提供してくれるサービスとして、「OpenStreetMap」を利用しています。OpenStreetMapでは、Open Data Commons Open Database Licenseというライセンスのもとであれば、地図データを自由に利用することができます。ルート探索機能では、「Open Source Routing Machine(OSRM)」というオープンソースのルート探索システムを用いて、ルート探索を行っています。
LeafretがOpenStreetMapに対応、Leaflet Routing MachineがOSRMに対応しているため、特に何か対応コードなどを書かなくても地図とルートを表示できます。
ただしGoogleMapと比較すると、OpenStreetMapは地図データのみなので、公共交通機関を用いたルート探索はできなくなってしまいます。また、車の場合には渋滞情報なども全く考慮されないので、到着時間はあまり正確ではないかもしれません。
サンプルでは、まずOpenStreetMapではtile.openstreetmap.orgの画像を使用しています。OpenStreetMapの地図データ自体はライセンスのもとで全く自由に使えるのですが、サーバ側は、例を挙げるとWikipediaのように広告なしで寄付によって運営されているサーバになります。ですので、高負荷をかけたりすることは利用規約によって禁止されています。またサンプルのルート探索機能ではOpen Source Routing Machineのデモサーバを利用しています。こちらもデモサーバであるので、高負荷をかけたりすることは利用規約によって禁止されています。
つまり、サービスとしてルート探索機能付き地図を提供するためには、これらの「地図画像提供サーバ」と、「ルート探索機能サーバ」をどうにかして別途用意する必要があります。
まず、すべて自前ではなく別途既存サービスを利用することを考えたいと思います。
地図画像を提供してくれるサービスとしては、OpenStreetMapで紹介されているとおり、有償のサービスがいくつもありますので、こちらを利用することがまず考えられます。また、しっかりとどのくらい負荷がかかるのかを確認した上で、tile.openstreetmap.orgとの間にCDNを挟んで利用するということも考えられます。ただし、tile.openstreetmap.orgは先程も申し上げたとおり寄付によって運営されているサーバで、SLAはないと思われるのでそこには注意してください。
ルート探索機能を提供してくれるサービスとしては、Leaflet Routing Machineが対応しているOSRM以外の、GraphHopperなどを利用するというのが考えられます。
これらを利用すれば、「地図画像提供で一番(早い/安い)サービス」と「ルート探索で一番(早い/安い)サービス」を組み合わせて利用することができ、またロックインを防ぐことができるというメリットがあるかと思います。
では…それらを自前で用意する場合はどうなるのでしょうか?
自前の地図画像サーバ
OpenStreetMapのサーバを立てるためには、少し古いですが以下の記事が参考になるかと思います。
OpenStreetMapサーバを自作する(Install OpenStreetMap on AWS EC2/Ubuntu14.04)
さて、ではこれをサーバレスで提供するにはどうしたらいいのでしょうか。
まずOpenStreetMapのタイルサーバがどのように動作しているのかご説明したいと思います。タイルサーバは、地図データを元にリクエストされた範囲の画像を動的に作成し、返却しています。
サーバレスで地図画像データを提供するには、2通り考えられます。
- S3にタイル画像をすべて配置しておく
- 予め自分でタイルサーバを構築し、リクエストを送りまくって画像を作成するというのが考えられます。タイルを落とすダウンローダやダンプするツールなども検索するとあるようなので、それを利用すると簡単に作れそうです。(繰り返しになりますがopenstreetmap.orgのサーバから大量に落とすのは規約違反なので、自分で構築したサーバから落とすようにしてください)
- Lambdaで動的にタイル画像を作成して返却する
- 予め地図データを細切れにしておきS3などに配置し、Lambdaで該当の箇所だけ読み込んで動的にタイル画像を作成して返すという手法です。細切れにするのは、メモリサイズを小さくして、更にS3からのダウンロードの時間を短くして実行時間を減らし、安価に済ませるためです。
- 画像を動的に作成できるので、何か地図画像に動的に仕込みたいといった場合は、こちらにする必要があると思います。
自前でのルート探索サーバ
サーバーレスではなく、Dockerで動かす場合は、OSRM公式のREADME.mdに記載されている方法でできるかと思います。
ではサーバーレスで行う場合を考えてみましょう。
OSRMでは、サーバとしてHTTPでやり取りする以外に、Node.jsのラッパライブラリとC++のライブラリが用意されています。てっとり早くやるには、Node.jsのラッパライブラリを使って、API GatewayとNode.jsのLambdaでOSRMのHTTP APIを模倣するような動きになるように実装するというのが考えられます。ちょっと手間ですが、単にドキュメントを見て同じように返すというだけなので、単にAPI Gatewayを組むだけなら本質的に難しいというわけではないかと思います。
しかし、本丸のルート探索を行うLambdaの実装は工夫がいります。
というのは、日本全国用にサービスを提供するのであれば、日本全国の道路グラフを読み込む必要があります。日本全国の地図データ(.osmファイル)は現在約1.3GBあります。これだけなら、タイル画像を動的に作るのと同じように、Lambdaを最大メモリサイズの3GBで立ち上げて、S3からファイルをメモリに読み込むようにすればギリギリいけるかも?とお考えかもしれませんが、これは単に地図データなので、OSRMでルート探索を行うには、地図データを道路のグラフデータ(.osrmファイル)に変換する必要があります。変換するとどうも4倍近くになるようなので、日本全国まるっと一気にLambda処理させるのは、現在では不可能です。
現在のjapan-latest.osm.pbf(約1.1GB)では、探索用データはトータルで約4.1GBになりました。
http://charilab.sakura.ne.jp/blog/2016/12/05/0002/
しかし、県・市といった単位、あるいは緯度経度で細切れにして.osrmファイルにし、単にそれだけを用いて経路探索を行うと、県や市をまたいだ経路検索ができなくなってしまいます。
複数の.osrmファイルを1つにする、というのも現時点ではサポートされておらず、また難しいようです。
1つだけ、完全な解決策ではないのではないのですが、実装方法を掲示したいと思います。
変換前の.osmファイルであれば、複数のファイルをマージすることができます。これを利用して、緯度経度などで正方形に細切れにした.osmファイルをLambda内で動的に .osrmファイルにし、そして経路検索を行う、という方法が考えられます。
この場合の問題点としては、緯度経度から直線に入っている.osmファイルのみをダウンロードするように実装した場合、メモリ容量、実行時間は短縮できるかと思いますが、例えば富士山を横断するようなルート探索をする場合に、.osmファイルをかなり細かく細切れにしていた場合は、表示したいであろう大きく迂回するようなルート探索ができなくなってしまうかと思います。
こういった問題を解消するために、地図をどれくらいのサイズで切るのか、どの地図をダウンロードするようなアルゴリズムにするのかといったことを、Lambdaのメモリ容量、実行時間と一緒に考えていく必要があり、ここのチューニングに大きく工数がかかるかと思います。
しかしこれらを解消できれば、もしかするとそれなりに安上がりに運用できる地図サービスができるのでは…と夢見ています。
おわりに
ちょっと探しきれなかったですが、フロントエンド側で道路グラフを計算して経路探索を行うというのも、不可能ではないと思います。
この記事を読んで、ちょっとこういうの考えるの面白いカモと思ったそこのアナタ、もしかするとサーバーレスに向いているのかもしれないです。さらにいいことに、現在我らがサーバーレス開発部はメンバーを大募集しています。
クラスメソッドが、またサーバーレス開発部が一体どんなところなのか、一度話だけでも聞いてみませんか?